package com.joymart.wechat.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.joymart.common.error.handling.JoymartInternalException;
import com.joymart.user.service.UserCacheManager;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WechatOpenPlatformService {
    @Value("${wechat.appId}")
    private String appId;
    @Value("${wechat.appSecret}")
    private String appSecret;
    @Value("${wechat.miniprogramState}")
    private String miniProgramState;

    @Autowired
    private RestTemplateBuilder restTemplateBuilder;
    @Autowired
    private ObjectMapper objectMapper;

    private LoadingCache<String, String> serverTokenCache;
    @Autowired
    private UserCacheManager userCacheManager;

    @PostConstruct
    private void init() {
        //在普通模式调用下，平台会提前5分钟更新access_token，
        // 即在有效期倒计时5分钟内发起调用会获取新的access_token。
        // 在新旧access_token交接之际，平台会保证在5分钟内，新旧access_token都可用，
        // 这保证了用户业务的平滑过渡； 根据此特性可知，任意时刻发起调用获取到的 access_token 有效期至少为 5 分钟，即expires_in >= 300；
        // 以上为微信官方文档， 我们服务器设置比微信官方少25秒。以避免边缘情况导致的token无效
        serverTokenCache = Caffeine.newBuilder()
                .expireAfterWrite(275, TimeUnit.SECONDS)  // 设置缓存的过期时间
                .maximumSize(1)  // 设置最大缓存数量
                .build(this::applyStableTokenFromWechat);
    }

    /**
     * @param userId
     * @param templateId 模板ID
     * @param data       数据
     * @param page       跳转页面
     */
    @Async
    @SneakyThrows
    public void sendSubscribeMessage(String userId, String templateId, Map<String, Object> data, @Nullable String page) {

        Optional<String> openId = Optional.ofNullable(userCacheManager.get(userId).getUser().getOpenId());
        if (openId.isEmpty()) {
            log.debug("user didn't bind openId {}", userId);
            return;
        }

        RestTemplate restTemplate = restTemplateBuilder.build();

        Map<String, Map<String, Object>> formattedData = new HashMap<>();

        data.forEach((k, v) -> formattedData.put(k, Map.of("value", v)));
        JSONObject body = new JSONObject()
                .put("template_id", templateId)
                .put("touser", openId.get())
                .put("data", formattedData)
                .put("miniprogram_state", miniProgramState)
                .put("lang", "zh_CN");
        if(StringUtils.hasText(page)) {
            body.put("page", page);
        }

        ResponseEntity<Map> res = restTemplate.postForEntity(
                "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}",
                objectMapper.writeValueAsString(body),
                Map.class,
                Map.of("access_token", serverTokenCache.get("access_token")));

        if (!res.getStatusCode().is2xxSuccessful()){
            log.info("send subscribe message failed - {}", res.getBody());
        }else if( !res.getBody().get("errmsg").equals("ok")){
            log.info("send subscribe message failed - {}", res.getBody().get("errmsg"));
        }else{
            log.debug("send subscribe message to user {} succeeded", userId);
        }

    }


    @SneakyThrows
    public Jscode2SessionResponse jsCode2Session(String code) {
        RestTemplate restTemplate = restTemplateBuilder.build();

        String res = restTemplate.getForObject(
                "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&grant_type=authorization_code&js_code={js_code}",
                String.class,
                Map.of("appid", appId, "secret", appSecret, "js_code", code));
        Jscode2SessionResponse authCodeResponse = objectMapper.readValue(res, Jscode2SessionResponse.class);
        return authCodeResponse;
    }


    /**
     * 获取不限制的小程序码
     * 接口应在服务器端调用，详细说明参见服务端API。
     */
    @SneakyThrows
    public byte[] getWXAcodeUnlimit(String envVersion, String page, int width, String scene) {
        RestTemplate restTemplate = restTemplateBuilder.build();

        ResponseEntity<byte[]> res = restTemplate.postForEntity(
                "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={access_token}",
                Map.of("page", page,
                        "width", width,
                        "env_version", envVersion,
                        "scene", scene,
                        "check_path", false,
                        "is_hyaline", true),
                byte[].class,
                Map.of("access_token", serverTokenCache.get("access_token")));
        String contentType = res.getHeaders().getContentType().toString();
        if (contentType.contains("application/json")) {
            // 如果是 JSON，反序列化为对象
            String jsonString = new String(res.getBody(), "UTF-8");
            JSONObject json = new JSONObject(jsonString); // 假设字符编码是 UTF-8
            log.warn("生成二维码失败 错误码：{},  错误信息：{}", json.get("errcode"), json.get("errmsg"));
            throw new JoymartInternalException("生成二维码失败");
        } else {
            // 如果是二进制数据，直接返回
            return res.getBody();
        }
    }


    private String applyStableTokenFromWechat(String whateverKey) {
        RestTemplate restTemplate = restTemplateBuilder.build();

        ResponseEntity<Map> mapResponseEntity = restTemplate.postForEntity(
                "https://api.weixin.qq.com/cgi-bin/stable_token",
                Map.of("appid", appId, "secret", appSecret, "grant_type", "client_credential", "force_refresh", false),
                Map.class);
        Map res = mapResponseEntity.getBody();
        return Optional.ofNullable(res)
                .map(r -> r.get("access_token")).map(Object::toString)
                .orElseThrow(() -> new JoymartInternalException("获取微信服务端Token失败"));

    }


    @Data
    public static class Jscode2SessionResponse {
        private String openid;
        private String session_key;
        private String errcode;
        private String errmsg;
        private String unionid;
    }

}
